#!/usr/bin/env python
# _*_coding:utf-8_*_
import json
import logging
from urllib.parse import quote, urlparse, urlunparse, parse_qsl, unquote

from django.db import transaction
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse
from rest_framework.response import Response
from rest_framework.views import APIView

from account.models import User
from common.decorators import Helper
from common.tools.money_cast import transforms_int
from common.tools.redis_tool import RedisStat
from common.views import AuthTemplateView, get_obj
from common.wx.WXBizMsgCrypt import WXBizMsgCrypt, WxCryptConfig
from common.wx.lib import WeixinHelper
from common.wx.pay import Notify_pub
from open.models import ComponentTicket, ComponentAccessToken, WxPublicInfo, PreAuthCode, WxPublicMapShop
from open.tools import get_public_token_and_info, refresh_public_token_and_info

logger = logging.getLogger('qixiaobao')


class AuthEventView(APIView):
    """授权事件和component_verify_ticket"""

    def post(self, request, *args, **kwargs):
        encry_type = request.query_params.get('encrypt_type', None)
        if encry_type == 'aes':
            if not {'msg_signature', 'timestamp', 'nonce'}.issubset(request.query_params):
                return Response('error')
            xml = request.body
            logger.debug('authevent:%s' % str(xml))
            # 使用通用通知接口
            notify = Notify_pub()
            notify.saveData(xml)
            result = notify.getData()

            msg_sign = request.query_params['msg_signature']
            timestamp = request.query_params['timestamp']
            nonce = request.query_params['nonce']
            print('query_params: ', request.query_params)
            logger.debug('xml = ' + str(xml))
            format_xml = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%s]]></Encrypt></xml>" % \
                         result['Encrypt']

            decrypt_1 = WXBizMsgCrypt(WxCryptConfig.token, WxCryptConfig.encodingAESKey_1, WxCryptConfig.app_id)
            decrypt_2 = WXBizMsgCrypt(WxCryptConfig.token, WxCryptConfig.encodingAESKey_2, WxCryptConfig.app_id)
            ret, decryp_xml = decrypt_1.DecryptMsg(format_xml.encode('utf8'), msg_sign, timestamp, nonce)

            if ret != 0:
                ret, decryp_xml = decrypt_2.DecryptMsg(format_xml.encode('utf8'), msg_sign, timestamp, nonce)
            # logger.debug('ret: ')
            # logger.debug(ret)
            # logger.debug('解密后xml : %s' % decryp_xml)
            if ret == 0:
                notify.saveData(decryp_xml)
                result = notify.getData()
                # logger.debug(str(result))
                info_type = result['InfoType']
                if info_type == 'component_verify_ticket':
                    component_verify_ticket = result['ComponentVerifyTicket']
                    ticket_list = ComponentTicket.objects.filter(status=ComponentTicket.STATUS_VALID)
                    if ticket_list.exists():
                        ticket = ticket_list.first()
                        ticket.ticket = component_verify_ticket
                        ticket.timestamp = result['CreateTime']
                        ticket.save()
                    else:
                        ComponentTicket.objects.create(ticket=component_verify_ticket,
                                                       status=ComponentTicket.STATUS_VALID)
                response = HttpResponse()
                response.write('success')
                return response
            return Response(ret)
        else:
            pass

        response = HttpResponse()
        response.write('success')
        return response


class GHView(AuthTemplateView):
    """
    展示绑定公众号，处理回调逻辑：根据授权码获取微信公众号token和信息 ；刷新微信公众号token和信息
    """
    template_name = 'open/gh_auth.html'

    def get_context_data(self, **kwargs):
        kwargs['secure'] = self.request.is_secure()
        wpm = get_obj(WxPublicMapShop, shop=self.shop, status=WxPublicMapShop.STATUS_VALID)
        if wpm:
            wpi = get_obj(WxPublicInfo, app_id=wpm.app_id,
                          wx_type=WxPublicInfo.WX_PUBLISH,
                          status=WxPublicInfo.STATUS_VALID)
            if wpi:
                kwargs['gh_info'] = wpi

        return super(GHView, self).get_context_data(**kwargs)

    def get(self, request, *args, **kwargs):
        # 根据授权码获取微信公众号token和信息
        auth_code = request.GET.get('auth_code', None)
        action = request.GET.get('action', None)
        if auth_code:
            logger.debug('获取公众号token和信息···')
            get_public_token_and_info(auth_code, self.shop.id)
        if action == 'refresh':
            logger.debug('刷新公众号token和信息···')
            refresh_public_token_and_info(self.shop.id)
        self.version = request.GET.get('version', None)
        return super(GHView, self).get(request, *args, **kwargs)


class GHRedirectView(AuthTemplateView):
    """
    组装参数和链接重定向到微信授权页面
    """
    template_name = ''

    def get(self, request, *args, **kwargs):
        if False:  # request.is_secure():
            domain_url = 'https://qxb.dongpouu.com'
        else:
            domain_url = 'http://qxb.dongpouu.com'

        auth_type = transforms_int(request.GET.get('auth_type', 3))
        url = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid={0}&pre_auth_code={' \
              '1}&redirect_uri={2}&auth_type={3} '
        if auth_type == WxPublicInfo.WX_XCX:
            redirect_uri = '%s%s' % (domain_url, reverse('open:gh_auth', args=[self.shop.id]))
        else:
            redirect_uri = '%s%s' % (domain_url, reverse('open:gh_auth', args=[self.shop.id]))
        # 刷新ComponentAccessToken
        token = ComponentAccessToken.objects.filter(status=ComponentAccessToken.STATUS_VALID).first().token
        # 获取预授权码
        data = json.loads(WeixinHelper.getPreAuthCode(WxCryptConfig.app_id, token))
        if 'pre_auth_code' in data:
            code = data['pre_auth_code']
        else:
            code = PreAuthCode.objects.first().code
        # 重定向到微信授权页面，授权成功后回调到redirect_uri
        return HttpResponseRedirect(url.format(WxCryptConfig.app_id, code, redirect_uri, auth_type))


class GHSnsView(APIView):
    """根据code获取授权和用户信息，并保存到redis"""

    def get(self, request, *args, **kwargs):
        if not {'code', 'state', 'appid'}.issubset(request.query_params):
            logger.debug('sns_base error: %s' % str(request.query_params))
            return Response()

        if request.is_secure():
            domain_url = 'https://' + request.META['HTTP_HOST']
        else:
            domain_url = 'http://' + request.META['HTTP_HOST']
        domain_url += '/open/gh/sns'

        code = request.query_params['code']
        state = request.query_params['state']
        appid = request.query_params['appid']

        state = unquote(state)

        state_list = state.split('|')
        # (base|info)|最近一级上级推客|转发的用户id|时间戳|店铺id|url存储id
        if len(state_list) != 6:
            logger.debug('WeixinHelper.openGetAccessToken error: appid: %s, state:%s' % (appid, state))
            return Response()

        r = RedisStat()
        # 在state上取出装饰器所在view存储在redis的url_id，并从redis去除url以备授权完成重定向用
        url = r.get('redirect_url_id_%s' % state_list[5])
        if not url:
            url = reverse('mobile_errors')

        if state_list[0] == 'base':
            # 通过code获取access_token和openid
            openid, access_token, expires_in, refresh_token = self.get_access_token(appid, code)
            if not openid:
                # 通过code查找用户信息
                redis_code = r.get(code)
                if redis_code:
                    ws = json.loads(redis_code)
                    state = '|'.join([state_list[1], state_list[2], state_list[3]])
                    request.session['show_' + state_list[4]] = Helper.sign_cookie(ws['openid'])
                    return HttpResponseRedirect(self.replace_state(url, state))
                else:
                    logger.debug('no find base code: %s' % code)
                    url = WeixinHelper.openOauth2(appid,
                                                  WxCryptConfig.app_id,
                                                  domain_url,
                                                  scope='snsapi_base',
                                                  state=state)
                    return HttpResponseRedirect(url)

            # 根据openid在redis查找用户信息
            redis_openid = r.get(openid)
            if not redis_openid:
                ws = {'app_id': appid, 'openid': openid, 'code': code, 'access_token': access_token,
                      'refresh_token': refresh_token, 'user_id': ''}
                json_string = json.dumps(ws)
                r.set(openid, json_string)
                r.set_ttl(code, json_string, 60)
            else:
                ws = json.loads(redis_openid)
            # 当没有user_id的时候说明未在系统内注册该用户，重定向到snsapi_userinfo获取授权并注册用户
            if ws['user_id']:
                state = '|'.join([state_list[1], state_list[2], state_list[3]])
                request.session['show_' + state_list[4]] = Helper.sign_cookie(openid)
                return HttpResponseRedirect(self.replace_state(url, state))
            else:
                state_list[0] = 'info'
                return HttpResponseRedirect(WeixinHelper.openOauth2(appid,
                                                                    WxCryptConfig.app_id,
                                                                    domain_url,
                                                                    scope='snsapi_userinfo',
                                                                    state='|'.join(state_list)))

        elif state_list[0] == 'info':
            # 通过接口获取access token
            openid, access_token, expires_in, refresh_token = self.get_access_token(appid, code)
            if not openid:
                # 通过code查找用户信息
                redis_code = r.get(code)
                if redis_code:
                    ws = json.loads(redis_code)
                    state = '|'.join([state_list[1], state_list[2], state_list[3]])
                    request.session['show_' + state_list[4]] = Helper.sign_cookie(ws['openid']) # 怀疑这一句就是登录逻辑。
                    return HttpResponseRedirect(self.replace_state(url, state))
                else:
                    # 重新进行base认证
                    logger.debug('no find info code: %s' % code)
                    state_list[0] = 'base'
                    url = WeixinHelper.openOauth2(appid,
                                                  WxCryptConfig.app_id,
                                                  domain_url,
                                                  scope='snsapi_base',
                                                  state='|'.join(state_list))
                    return HttpResponseRedirect(url)

            # 根据openid查找用户
            redis_openid = r.get(openid)
            if not redis_openid:
                ws = {'app_id': appid, 'openid': openid, 'code': code, 'access_token': access_token,
                      'refresh_token': refresh_token, 'user_id': ''}
                json_string = json.dumps(ws)
                r.set(openid, json_string)
                r.set_ttl(code, json_string, 60)
            else:
                ws = json.loads(redis_openid)

            # 更新用户信息
            nick, headimg, unionid, sex = self.get_user_info(openid, access_token)
            user_id = self.update_user_info(openid, nick, headimg, unionid, sex)
            if not ws['user_id'] == user_id:
                ws['user_id'] = user_id
                json_string = json.dumps(ws)
                r.set(openid, json_string)
                r.set_ttl(code, json_string, 60)

            state = '|'.join([state_list[1], state_list[2], state_list[3]])
            request.session['show_' + state_list[4]] = Helper.sign_cookie(openid)
            return HttpResponseRedirect(self.replace_state(url, state))
        return HttpResponseRedirect(url)

    @staticmethod
    def get_access_token(appid, code):
        token = ComponentAccessToken.objects.first().token
        data = json.loads(WeixinHelper.openGetAccessToken(appid, code, WxCryptConfig.app_id, token))
        if 'access_token' not in data:
            logger.debug(
                'PublicSnsView WeixinHelper.openGetAccessToken error: appid: %s, token: %s, data:%s' % (
                    appid, token, str(data)))
            return '', '', '', ''

        access_token = data['access_token']
        expires_in = data['expires_in']
        refresh_token = data['refresh_token']
        openid = data['openid']
        return openid, access_token, expires_in, refresh_token

    @staticmethod
    def get_user_info(openid, token):
        data = json.loads(WeixinHelper.getSnsapiUserInfo(token, openid))
        if 'openid' in data:
            if 'unionid' in data:
                return data['nickname'], data['headimgurl'], data['unionid'], data['sex']
            return data['nickname'], data['headimgurl'], '', data['sex']
        return None, None, '', 0

    @staticmethod
    def update_user_info(openid, nick, headimg, unionid, sex):
        if openid:
            if nick is None and headimg is None:
                return ''
            user = None
            if unionid:
                user = get_obj(User, unionid=unionid, status=User.STATUS_VALID)
            if not user:
                user = get_obj(User, gh_openid=openid, status=User.STATUS_VALID)

            if user:
                user.openid = openid
                user.nick = nick
                user.logo = headimg
                user.sex = sex
                user.unionid = unionid
                user.save()
                return user.id
            else:
                try:
                    with transaction.atomic():
                        user = User.objects.create_user(username=openid, password=openid,
                                                        gh_openid=openid, unionid=unionid,
                                                        nick=nick, logo=headimg,
                                                        sex=sex, status=User.STATUS_VALID)
                        return user.id
                except Exception as e:
                    pass
        return ''

    @staticmethod
    def replace_state(url, state):
        parse = urlparse(url)
        query = parse.query.replace('amp;'.encode('utf8'), ''.encode('utf8'))
        params_dict = dict(parse_qsl(query))
        params_dict['state'.encode('utf8')] = quote(state).encode('utf8')
        logger.debug('params_dict````````````')
        logger.debug(params_dict)
        query = '&'.join([key.decode('utf8') + '=' + value.decode('utf8') for key, value in params_dict.items()])
        return urlunparse((parse.scheme, parse.netloc, parse.path, parse.params, query.encode('utf8'), parse.fragment))
